package com.bestv.bsr.core.aspect;

import cn.hutool.core.util.StrUtil;
import com.bestv.bsr.core.annotations.RedisLock;
import com.bestv.bsr.core.utils.SpElUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;


/**
 * 使用redis进行分布式锁
 *
 * @author taojiacheng
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class RedisLockAspect {

    /**
     * redis 锁
     */
    private static final String REDISSON_LOCK_PREFIX = "redisson_lock:%s";

    private final RedissonClient redissonClient;

    @Around("@annotation(redisLock)")
    public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        String spEl = redisLock.key();
        String lockName = redisLock.lockName();
        String key = getRedisKey(joinPoint, lockName, spEl);
        RLock rLock = redissonClient.getLock(key);
        Object result = null;
        try {
            if (rLock.tryLock(redisLock.waitTime(), redisLock.expire(), redisLock.timeUnit())) {
                log.info("lock key: {}", key);
                //执行方法
                result = joinPoint.proceed();
            } else {
                log.info("is locked key: {}", key);
            }
        } catch (Throwable e) {
            log.error("redis lock aspect error", e);
            Thread.currentThread().interrupt();
            throw e;
        }
        return result;
    }

    /**
     * 将spEl表达式转换为字符串
     *
     * @param joinPoint 切点
     * @return redisKey
     */
    private String getRedisKey(ProceedingJoinPoint joinPoint, String lockName, String spEl) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Object target = joinPoint.getTarget();
        Object[] arguments = joinPoint.getArgs();
        return String.format(REDISSON_LOCK_PREFIX, lockName + StrUtil.COLON + SpElUtil.parse(target, spEl, targetMethod, arguments));
    }
}
